/* 
 * s2250mid.c
 * An API layer for 2250 using Video4Linux2
 * Copyright(c) 2010 Sensoray Co., Inc.
 */

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <alsa/asoundlib.h>
#include <pthread.h>
#include "s2250mid.h"

/* from include/linux/videodev2.h */
#ifndef V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC
#define V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC 2
#endif

/* MPEG-4 compressed video elementary stream*/
#ifndef V4L2_PIX_FMT_MP4V
#define V4L2_PIX_FMT_MP4V v4l2_fourcc('M', 'P', '4', 'V')
#endif

/* this must match name in go7007_usb_probe in driver/go7007-usb.c */
static const char *cardname = "Sensoray 2250/2251";

static int G_debug = 0;

struct board {
	int fd; // file descriptor of board
	pthread_mutex_t mutex;
	int blocking; // blocking on/off
	int encodetype; // MIDSRAY_ENCODE_*
	int capturing;
	int n_buffers;
	struct {
		void *start;
		size_t length;
	} buffers[4];
	
	snd_pcm_t *pcm;
	snd_pcm_hw_params_t *hwparams;
	snd_pcm_sw_params_t *swparams;
};

static struct board board[MAX_BOARDS] = {
	[0 ... MAX_BOARDS-1] = { /* GCC designated initializers */
		.fd = -1,
		.mutex = PTHREAD_MUTEX_INITIALIZER,
	}
};


/*------------------- private functions ----------------------*/

/* print a message if debugging is enabled */
static void dprintk(const char *fmt, ...)
{
	va_list ap;
	if (!G_debug)
		return;
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
}


/* do an ioctl and translate the errno */
static int xioctl(int fd, int request, void *arg)
{
	int rc;
	
	do
		rc = ioctl(fd, request, arg);
	while (rc == -1 && errno == EINTR);
	return rc == -1 ? -errno : rc;
}


/* if boardnum == -1, return total number of boards, 
   else return open fd of that board or -1 if not found */
static int EnumVideo(int boardnum)
{
	int i, fd, rc;
	char devname[16];
	struct v4l2_capability cap;

	for (i = 0; i < 256; i++) {
		sprintf(devname, "/dev/video%d", i);
		fd = open(devname, O_RDWR);
		if (fd == -1)
			continue;
		if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
			dprintk("%s: %s card %s\n", __func__, devname, cap.card);
			if (strcmp(cap.card, cardname) == 0)
				if (boardnum-- == 0)
					return fd;
		}
		close(fd);
	}
	return boardnum < 0 ? -1 - boardnum : -1;
}

/* get a board if it is open, and lock its mutex if successful */
static struct board *GetBoard(int boardnum)
{
	if ((unsigned int) boardnum > MAX_BOARDS)
		return NULL;
	pthread_mutex_lock(&board[boardnum].mutex);
	if (board[boardnum].fd == -1) {
		pthread_mutex_unlock(&board[boardnum].mutex);
		dprintk("%s: board %d not open\n", __func__, boardnum);
		return NULL;
	}
	return &board[boardnum];
}

/* unlock a board after a successful GetBoard */
static void PutBoard(struct board *brd)
{
	if (brd)
		pthread_mutex_unlock(&brd->mutex);
}


/* request and intialize the mmap video buffers */
static int InitMmap(struct board *brd)
{
	int rc, i;
	struct v4l2_requestbuffers req;

	/* unmap buffers that are already mapped */
	for (i = 0; i < brd->n_buffers; i++)
		munmap(brd->buffers[i].start, brd->buffers[i].length);
	
	req.count = 4;
	req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	req.memory = V4L2_MEMORY_MMAP;
	
	rc = xioctl(brd->fd, VIDIOC_REQBUFS, &req);
	if (rc < 0) {
		dprintk("%s: REQBUFS error %d\n", __func__, rc);
		return rc;
	}
	
	brd->n_buffers = req.count;
	for (i = 0; i < req.count; i++) {
		struct v4l2_buffer buf = {
			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
			.memory = V4L2_MEMORY_MMAP,
			.index = i,
		};
		rc = xioctl(brd->fd, VIDIOC_QUERYBUF, &buf);
		if (rc < 0) {
			dprintk("%s: QUERYBUF error %d\n", __func__, rc);
			return rc;
		}
		brd->buffers[i].length = buf.length;
		brd->buffers[i].start = mmap(NULL, buf.length,
					PROT_READ | PROT_WRITE,
					MAP_SHARED,
					brd->fd, buf.m.offset);
		if (brd->buffers[i].start == MAP_FAILED) {
			dprintk("%s: mmap failed\n", __func__);
			return -EFAULT;
		}
	}
	return 0;
}

/* close and free pcm related structures */
static void FreePCM(struct board *brd)
{
	if (brd->pcm)
		snd_pcm_close(brd->pcm);
	if (brd->hwparams)
		snd_pcm_hw_params_free(brd->hwparams);
	if (brd->swparams)
		snd_pcm_sw_params_free(brd->swparams);
	brd->pcm = NULL;
	brd->hwparams = NULL;
	brd->swparams = NULL;
}

/* open and initialize the board pcm (it will be NULL on failure) */
static void EnumPCM(struct board *brd, int boardnum)
{
	int rc, i;
	char name[16];
	snd_pcm_info_t *info;
	
	rc = snd_pcm_info_malloc(&info);
	if (rc)
		return;
	i = 0;
	for (i = 0; rc == 0; i++) {
		sprintf(name, "hw:%d,0", i);
		rc = snd_pcm_open(&brd->pcm, name, SND_PCM_STREAM_CAPTURE, 
			brd->blocking ? 0 : SND_PCM_NONBLOCK);
		if (rc) {
			dprintk("%s: snd_pcm_open(%s..) error %d\n", __func__, name, rc);
			goto cleanup;
		}
		rc = snd_pcm_info(brd->pcm, info);
		if (rc)
			dprintk("%s: snd_pcm_info error %d\n", __func__, rc);
		else if (0 == strcmp(snd_pcm_info_get_id(info), "go7007")) {
			if (boardnum-- == 0)
				break;
		}
		snd_pcm_close(brd->pcm);
		brd->pcm = NULL;
	}
	if (brd->pcm) {
		unsigned int rate = 48000;
		snd_pcm_uframes_t period = 256;

		dprintk("%s: found pcm %s %s\n", __func__, 
			snd_pcm_name(brd->pcm),
			snd_pcm_info_get_id(info));
			
		rc = snd_pcm_hw_params_malloc(&brd->hwparams);
		if (rc)
			goto cleanup;
		rc = snd_pcm_sw_params_malloc(&brd->swparams);
		if (rc)
			goto cleanup;

		rc = snd_pcm_hw_params_any(brd->pcm, brd->hwparams);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params_set_rate_resample(brd->pcm, brd->hwparams, 1);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params_set_access(brd->pcm, brd->hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params_set_format(brd->pcm, brd->hwparams, SND_PCM_FORMAT_S16_LE);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params_set_channels(brd->pcm, brd->hwparams, 2);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params_set_rate_near(brd->pcm, brd->hwparams, &rate, NULL);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params_set_period_size_near(brd->pcm, brd->hwparams, &period, NULL);
		if (rc)
			goto cleanup;
		rc = snd_pcm_hw_params(brd->pcm, brd->hwparams);
		if (rc)
			goto cleanup;
			
		rc = snd_pcm_sw_params_current(brd->pcm, brd->swparams);
		if (rc)
			goto cleanup;
		rc = snd_pcm_sw_params(brd->pcm, brd->swparams);
		if (rc) {
cleanup:
			dprintk("%s: pcm config error %d\n", __func__, rc);
			FreePCM(brd);
		}
	}
	snd_pcm_info_free(info);
}


/*--------------------- public API functions -------------------*/

/* detect number of boards plugged in */
/* rescan every time */
DLLCP int SN_EnumBoards(void)
{
	int boards = EnumVideo(-1);
	dprintk("%s: %d boards\n", __func__, boards);
	return boards;
}

/* open 2250 board */
DLLCP int SN_OpenBoard(int boardnum)
{
	struct board *brd;
	int rc = 0;
	
	if ((unsigned int) boardnum > MAX_BOARDS)
		return -ENODEV;
	brd = &board[boardnum];
	pthread_mutex_lock(&brd->mutex);
	if (brd->fd != -1) {
		dprintk("%s: board %d already open with fd %d\n", __func__, boardnum, brd->fd);
		goto unlock; /* already open */
	}

	brd->pcm = NULL;
	brd->hwparams = NULL;
	brd->swparams = NULL;
	/* PCM will be enumerated by SN_SetBlocking */

	brd->n_buffers = 0;
	brd->capturing = 0;
	
	brd->fd = EnumVideo(boardnum);
	if (brd->fd == -1) {
		dprintk("%s: board %d doesn't exist\n", __func__, boardnum);
		rc = -ENODEV;
		goto unlock;
	}
	dprintk("%s: board %d opened with fd %d\n", __func__, boardnum, brd->fd);
	pthread_mutex_unlock(&brd->mutex);

	/* perform board initialization */
	SN_SetBlocking(boardnum);
	rc = SN_SetBasicChipSettings(boardnum,
					MIDSRAY_ENCODE_MPEG4,
					0,
					MIDSRAY_VIDSYS_NTSC,
					2000000,
					0);
	if (rc != 0) {
		dprintk("%s: SN_SetBasicChipSettings error %d\n", __func__, rc);
		return rc;
	}
	return rc;
unlock:
	pthread_mutex_unlock(&brd->mutex);
	return rc;
}

/* close 2250 board */
DLLCP int SN_CloseBoard(int boardnum)
{
	struct board *brd = GetBoard(boardnum);
	int rc = 0, i;

	if (!brd)
		return -ENODEV;

	if (brd->capturing) {
		pthread_mutex_unlock(&brd->mutex);
		SN_StopStream(boardnum);
		pthread_mutex_lock(&brd->mutex);
	}

	dprintk("%s: board %d closing fd %d\n", __func__, boardnum, brd->fd);

	for (i = 0; i < brd->n_buffers; i++)
		munmap(brd->buffers[i].start, brd->buffers[i].length);
	brd->n_buffers = 0;

	close(brd->fd);
	brd->fd = -1;

	FreePCM(brd);
	
	PutBoard(brd);
	return rc;
}

/* set the basic chip settings
 * Note: settings are not updated until stream started
 * (ie. if board running, must stop and restart to take effect).
 * video system and iVidSize determine the video size.  
 * @param boardnum board number
 * @param encodetype (see MIDSRAY_ENCODING)
 * @param iVidSize iVidSize == 0(full size), iVidSize ==1 (half size)
 * @param vsys video system
 * @param bitrate desired bitrate (default 2Mbit/s).
 * For iVidSize=1(half size), max bitrate is 2MBit/s.
 *  @param bIFramesOnly (default off=0).  To use IFrames only in the stream.
 */

DLLCP int SN_SetBasicChipSettings(int boardnum, MIDSRAY_ENCODING encodetype, 
	int iVidSize, MIDSRAY_VIDSYS vsys, int bitrate, int bIFramesOnly )
{
	struct board *brd = GetBoard(boardnum);
	int rc = 0;
	int width, height;
	v4l2_std_id std;
	struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
	struct v4l2_ext_control ctrl[4];
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 0,
		.reserved = { 0, 0 },
		.controls = &ctrl[0],
	};

	if (!brd)
		return -ENODEV;

	std = (vsys == MIDSRAY_VIDSYS_PAL) ? V4L2_STD_PAL : V4L2_STD_NTSC;
	width = (iVidSize == 0) ? 720 : 352;
	height = (vsys == MIDSRAY_VIDSYS_PAL) ? 576 : 480;
	if (iVidSize != 0) height /= 2;
	
	dprintk("%s: encodetype=%d width=%d height=%d vsys=%d bitrate=%d bIFrames=%d\n",
		__func__, encodetype, width, height, vsys, bitrate, bIFramesOnly);
	ctrl[ctrls.count].id = V4L2_CID_MPEG_VIDEO_BITRATE;
	ctrl[ctrls.count].value = bitrate;
	ctrls.count++;
	
	if (encodetype != MIDSRAY_ENCODE_MOTIONJPEG) {
		ctrl[ctrls.count].id = V4L2_CID_MPEG_VIDEO_ENCODING;
		if (encodetype == MIDSRAY_ENCODE_MPEG1)
			ctrl[ctrls.count].value = V4L2_MPEG_VIDEO_ENCODING_MPEG_1;
		else if (encodetype == MIDSRAY_ENCODE_MPEG2)
			ctrl[ctrls.count].value = V4L2_MPEG_VIDEO_ENCODING_MPEG_2;
		else if (encodetype == MIDSRAY_ENCODE_MPEG4)
			ctrl[ctrls.count].value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC;
		else if (encodetype == MIDSRAY_ENCODE_MPEG4DIVX) {
			dprintk("%s: encodetype MPEG4DIVX not supported: using MPEG4\n", __func__);
			ctrl[ctrls.count].value = V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC;
		}
		ctrls.count++;
	}
	if (bIFramesOnly) {
		ctrl[ctrls.count].id = V4L2_CID_MPEG_VIDEO_B_FRAMES;
		ctrl[ctrls.count].value = 0;
		ctrls.count++;
		ctrl[ctrls.count].id = V4L2_CID_MPEG_VIDEO_GOP_SIZE;
		ctrl[ctrls.count].value = 1;
		ctrls.count++;
	}
	rc = ioctl(brd->fd, VIDIOC_G_FMT, &fmt);
	if (rc < 0) {
		dprintk("%s: G_FMT error %d\n", __func__, rc);
		goto unlock;
	}
	fmt.fmt.pix.width = width;
	fmt.fmt.pix.height = height;
	fmt.fmt.pix.pixelformat = (encodetype == MIDSRAY_ENCODE_MOTIONJPEG) ?
				V4L2_PIX_FMT_MJPEG : V4L2_PIX_FMT_MPEG;
	rc = ioctl(brd->fd, VIDIOC_S_FMT, &fmt);
	if (rc < 0) {
		dprintk("%s: S_FMT error %d\n", __func__, rc);
		goto unlock;
	}
	/* set controls after format, otherwise gop_size stays at 0 */
	rc = xioctl(brd->fd, VIDIOC_S_EXT_CTRLS, &ctrls);
	if (rc < 0) {
		dprintk("%s: S_EXT_CTRLS error %d\n", __func__, rc);
		goto unlock;
	}
	brd->encodetype = encodetype;

	rc = xioctl(brd->fd, VIDIOC_S_STD, &std);
	if (rc < 0) {
		dprintk("%s: S_STD error %d\n", __func__, rc);
		goto unlock;
	}

	/* init mmap buffers */
	rc = InitMmap(brd);

unlock:
	PutBoard(brd);
	return rc;
}


/* start video and audio streams */
DLLCP int SN_StartStream(int boardnum)
{
	int rc;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	unsigned int i;

	dprintk("%s: board %d\n", __func__, boardnum);
	/* queue all buffers */
	for (i = 0; i < brd->n_buffers; i++) {
		struct v4l2_buffer buf = {
			.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
			.memory = V4L2_MEMORY_MMAP,
			.index = i,
		};
		rc = xioctl(brd->fd, VIDIOC_QBUF, &buf);
		if (rc < 0)
			goto unlock;
	}

	rc = xioctl(brd->fd, VIDIOC_STREAMON, &type);
	if (rc == 0) {
		brd->capturing = 1;
		if (brd->pcm)
			rc = snd_pcm_start(brd->pcm);
	}
unlock:
	PutBoard(brd);
	return rc;
}

/* stop streams */
DLLCP int SN_StopStream(int boardnum)
{
	int rc;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	dprintk("%s: board %d\n", __func__, boardnum);
	rc = xioctl(brd->fd, VIDIOC_STREAMOFF, &type);
	if (rc == 0)
		brd->capturing = 0;

	if (brd->pcm)
		snd_pcm_drop(brd->pcm); /* stop and drop pending frames */
		
	
	PutBoard(brd);
	return rc;
}


/* get one frame of video data in pBuf.  
 * BufLen specified in BufLen. size of frame returns in pFI->data_length
 */
DLLCP int SN_GetOneFrame(int boardnum, unsigned char *pBuf, int BufLen, frame_info_t *pFI)
{
	int rc = 0;
	struct v4l2_buffer buf = {
		.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
		.memory = V4L2_MEMORY_MMAP,
	};
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	if (!brd->capturing || !pBuf || BufLen < 0) {
		rc = -EFAULT;
		goto unlock;
	}
	
	if (brd->blocking) 
		for (;;) {
			fd_set fds;
			struct timeval tv;

			FD_ZERO(&fds);
			FD_SET(brd->fd, &fds);

			tv.tv_sec = 1; /* one second timeout */
			tv.tv_usec = 0;

			pthread_mutex_unlock(&brd->mutex);
			rc = select(brd->fd + 1, &fds, NULL, NULL, &tv);
			pthread_mutex_lock(&brd->mutex);

			if (rc > 0)
				break; /* data ready */

			if (rc == 0) {
				rc = -EAGAIN; /* timeout */
				goto unlock;
			}
			rc = -errno;
			if (rc != -EINTR)
				goto unlock; /* other error */
		}

	rc = xioctl(brd->fd, VIDIOC_DQBUF, &buf);

	if (rc < 0)
		goto unlock;

	if (buf.bytesused < BufLen)
		BufLen = buf.bytesused;
	memcpy(pBuf, brd->buffers[buf.index].start, BufLen);

	if (pFI) {
		pFI->timestamp = buf.timestamp;
		pFI->data_length = buf.bytesused;
#if 0		/* driver can't return the frame type */
		pFI->type = buf.flags & V4L2_BUF_FLAG_PFRAME ? 1 :
			    buf.flags & V4L2_BUF_FLAG_BFRAME ? 2 : 0;
#endif
		/* examine buf based on format to determine frame type */
		switch (brd->encodetype) {
		case MIDSRAY_ENCODE_MOTIONJPEG:
			pFI->type = 0;
			break;
		case MIDSRAY_ENCODE_MPEG4:
		case MIDSRAY_ENCODE_MPEG4DIVX:
			switch ((pBuf[4] >> 6) & 0x3) {
			default:
				pFI->type = 0;
				break;
			case 1:
				pFI->type = 1;
				break;
			case 2:
				pFI->type = 2;
				break;
			}
			break;
		case MIDSRAY_ENCODE_MPEG1:
		case MIDSRAY_ENCODE_MPEG2:
			switch ((pBuf[5] >> 3) & 0x7) {
			default:
				pFI->type = 0;
				break;
			case 2:
				pFI->type = 1;
				break;
			case 3:
				pFI->type = 2;
				break;
			}
			break;
		}
	}

	rc = xioctl(brd->fd, VIDIOC_QBUF, &buf);
unlock:
	PutBoard(brd);
	return rc;
}


/* get audio samples in pBuf.  Set buffer size in BufLen.
 *  Returns number of samples in BufLen
 */
DLLCP int SN_GetAudioData( int boardnum, unsigned char *pBuf, int *BufLen)
{
	int rc;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	if (!brd->pcm)
		EnumPCM(brd, boardnum);
	if (!brd->pcm)
		rc = -EBUSY;
	else if (!pBuf || !BufLen)
		rc = -EFAULT;
	else {
		int numSamples = (*BufLen) >> 2;
		/* snd_pcm_readi may block, so unlock the mutex to allow
		   start_stream to be called (which starts audio too) */
		pthread_mutex_unlock(&brd->mutex);
		do {
			rc = snd_pcm_readi(brd->pcm, pBuf, numSamples);
			if (rc != -EPIPE)
				break;
			rc = snd_pcm_prepare(brd->pcm); // xrun recovery
		} while (rc == 0);
		pthread_mutex_lock(&brd->mutex);
		//dprintk("%s: read %d frames\n", __func__, rc);
		*BufLen = rc >= 0 ? rc * 4 : 0;
	}
	PutBoard(brd);
	return rc;
}


/* same as above, but returns a timestamp with the data
 * (Note: timestamp is best effort.  accuracy is not specified.)
 */
DLLCP int SN_GetAudioDataPlusTime( int boardnum, unsigned char *pBuf, int *BufLen, int *tv_sec, int *tv_usec)
{
	int rc;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	if (!brd->pcm)
		EnumPCM(brd, boardnum);
	if (!brd->pcm)
		rc = -EBUSY;
	else if (!pBuf || !BufLen || !tv_sec || !tv_usec)
		rc = -EFAULT;
	else {
		int numSamples = (*BufLen) >> 2;
		/* snd_pcm_readi may block, so unlock the mutex to allow
		   start_stream to be called (which starts audio too) */
		pthread_mutex_unlock(&brd->mutex);
		do {
			rc = snd_pcm_readi(brd->pcm, pBuf, numSamples);
			if (rc != -EPIPE)
				break;
			rc = snd_pcm_prepare(brd->pcm); // xrun recovery
		} while (rc == 0);
		pthread_mutex_lock(&brd->mutex);
		*BufLen = (rc >= 0) ? rc * 4 : 0;
		if (rc >= 0) {
			snd_pcm_status_t *status;
			
			snd_pcm_status_alloca(&status);
			rc = snd_pcm_status(brd->pcm, status);
			if (rc == 0) {
				snd_timestamp_t ts;
				snd_pcm_status_get_trigger_tstamp(status, &ts);
				*tv_sec = ts.tv_sec;
				*tv_usec = ts.tv_usec;
			} else {
				dprintk("%s: snd_pcm_status error %d\n", __func__, rc);
			}
		} else {
			//perror("snd_pcm_readi");
			FreePCM(brd);
		}
		//dprintk("%s: read %d frames  %d:%d\n", __func__, rc, *tv_sec, *tv_usec);
	}
	PutBoard(brd);
	return rc;
}


/* set the video source 0 -- svideo, 1 -- composite(2250 only)
 */
DLLCP int SN_SetVideoSource( int boardnum, int source)
{
	int rc;
	struct board *brd = GetBoard(boardnum);
	int src;
	if (!brd)
		return -ENODEV;
	if (source & ~1) /* source must be 0 or 1 */
		rc = -EINVAL;
	else {
		/* inputs are reversed in driver */
		source ^= 1;
		rc = ioctl(brd->fd, VIDIOC_S_INPUT, &source);
	}
	PutBoard(brd);
	return rc;
}


/* set video system.  0--NTSC, 1 PAL */
DLLCP int SN_SetVideoSystem( int boardnum, MIDSRAY_VIDSYS vidsys)
{
	int rc;
	v4l2_std_id std;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;

	std = (vidsys == MIDSRAY_VIDSYS_PAL) ? V4L2_STD_PAL : V4L2_STD_NTSC;
	rc = ioctl(brd->fd, VIDIOC_S_STD, &std);

	PutBoard(brd);
	return rc;
}


/* levels bright 0-100 (default 50) */
DLLCP int SN_SetBrightness( int boardnum, int bright)
{
	int rc;
	struct v4l2_control ctrl;
	struct board *brd = GetBoard(boardnum);
	
	if (!brd)
		return -ENODEV;
	ctrl.id = V4L2_CID_BRIGHTNESS;
	ctrl.value = bright;
	rc = ioctl(brd->fd, VIDIOC_S_CTRL, &ctrl);

	PutBoard(brd);
	return rc;
	
}
/* contrast 0-100 (default 50) */
DLLCP int SN_SetContrast( int boardnum, int contrast)
{
	int rc;
	struct v4l2_control ctrl;
	struct board *brd = GetBoard(boardnum);
	
	if (!brd)
		return -ENODEV;
	ctrl.id = V4L2_CID_CONTRAST;
	ctrl.value = contrast;
	rc = ioctl(brd->fd, VIDIOC_S_CTRL, &ctrl);

	PutBoard(brd);
	return rc;
}

/* hue -50 to 50 (default 0) */
DLLCP int SN_SetHue( int boardnum, int hue)
{
	int rc;
	struct v4l2_control ctrl;
	struct board *brd = GetBoard(boardnum);
	
	if (!brd)
		return -ENODEV;
	ctrl.id = V4L2_CID_HUE;
	ctrl.value = hue;
	rc = ioctl(brd->fd, VIDIOC_S_CTRL, &ctrl);

	PutBoard(brd);
	return rc;
}

/* saturation 0-100 ( default 50) */
DLLCP int SN_SetSaturation( int boardnum, int sat)
{
	int rc;
	struct v4l2_control ctrl;
	struct board *brd = GetBoard(boardnum);
	
	if (!brd)
		return -ENODEV;
	ctrl.id = V4L2_CID_SATURATION;
	ctrl.value = sat;
	rc = ioctl(brd->fd, VIDIOC_S_CTRL, &ctrl);

	PutBoard(brd);
	return rc;
}

/* set caption text */
DLLCP int SN_SetCaption( int boardnum, int xstart, int ystart, char *text)
{
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;

	PutBoard(brd);
	return -EINVAL; /* not supported with V4L2 */
}

/* clear caption text */
DLLCP int SN_ClearCaption( int boardnum)
{
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;

	PutBoard(brd);
	return -EINVAL; /* not supported with V4L2 */
}

/* source = 0(default line), source=1(mic), source =2 (mic with boost) */
DLLCP int SN_SetAudioSource( int boardnum, int source)
{
	int rc;
	snd_mixer_t *rcMixer = NULL;
	snd_mixer_elem_t *elem = NULL;
	snd_mixer_selem_id_t *sid = NULL;
	char name[16];
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	if (!brd->pcm) {
		PutBoard(brd);
		return -ENODEV;
	}

	snd_mixer_selem_id_malloc(&sid);
	
	rc = snd_mixer_open(&rcMixer, 0);
	if (rc < 0) {
		dprintk("%s: snd_mixer_open error %d\n", __func__, rc);
		goto unlock;
	}
	strncpy(name, snd_pcm_name(brd->pcm), sizeof(name)-1);
	if (strlen(name) >= 2)
		name[strlen(name)-2] = 0;
	rc = snd_mixer_attach(rcMixer, name);
	if (rc < 0) {
		dprintk("%s: snd_mixer_attach %s error %d\n", __func__, name, rc);
		goto unlock;
	}
	rc = snd_mixer_selem_register(rcMixer, NULL, NULL);
	if (rc < 0) {
		dprintk("%s: snd_mixer_selem_register error %d\n", __func__, rc);
		goto unlock;
	}
	rc = snd_mixer_load(rcMixer);
	if (rc < 0) {
		dprintk("%s: snd_mixer_load error %d\n", __func__, rc);
		goto unlock;
	}
	rc = -EINVAL;
	for (elem = snd_mixer_first_elem(rcMixer); elem; elem = snd_mixer_elem_next(elem)) {
		snd_mixer_selem_get_id(elem, sid);
		if (!snd_mixer_selem_is_active(elem))
			continue;
		//dprintk("\n%s: snd_mixer_selem_get_name %s\n", __func__, snd_mixer_selem_get_name(elem));
		if (snd_mixer_selem_is_enum_capture(elem) &&
		    0 == strcmp(snd_mixer_selem_get_name(elem), "Input Source")) {
			//dprintk("%s: setting elem to %d\n", __func__, source);
			rc = snd_mixer_selem_set_enum_item(elem, SND_MIXER_SCHN_MONO, source);
			if (rc)
				dprintk("%s: snd_mixer_selem_set_enum_item %d error %d\n", __func__, source, rc);
		}
	}

unlock:
	if (sid)
		snd_mixer_selem_id_free(sid);
	if (rcMixer)
		snd_mixer_close(rcMixer);
	PutBoard(brd);
	return rc;
}


DLLCP int SN_SetHeight( int boardnum, int height)
{
	int rc;
	struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
	struct board *brd = GetBoard(boardnum);
	
	if (!brd)
		return -ENODEV;
	rc = ioctl(brd->fd, VIDIOC_G_FMT, &fmt);
	if (rc == 0) {
		fmt.fmt.pix.height = height;
		rc = ioctl(brd->fd, VIDIOC_S_FMT, &fmt);
	}

	PutBoard(brd);
	return rc;
}

DLLCP int SN_GetHeight( int boardnum, int *height)
{
	int rc;
	struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;	
	rc = ioctl(brd->fd, VIDIOC_G_FMT, &fmt);
	if (rc == 0 && height)
		*height = fmt.fmt.pix.height;

	PutBoard(brd);
	return rc;
}


DLLCP int SN_SetWidth( int boardnum, int width)
{
	int rc;
	struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	rc = ioctl(brd->fd, VIDIOC_G_FMT, &fmt);
	if (rc == 0) {
		fmt.fmt.pix.width = width;
		rc = ioctl(brd->fd, VIDIOC_S_FMT, &fmt);
	}

	PutBoard(brd);
	return rc;
}

DLLCP int SN_GetWidth( int boardnum, int *width)
{
	int rc;
	struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	rc = ioctl(brd->fd, VIDIOC_G_FMT, &fmt);
	if (rc == 0 && width)
		*width = fmt.fmt.pix.width;

	PutBoard(brd);
	return rc;
}

DLLCP int SN_SetBitrate( int boardnum, int bitrate)
{
	int rc;
	struct v4l2_ext_control ctrl = {
		.id = V4L2_CID_MPEG_VIDEO_BITRATE,
	};
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 1,
		.reserved = { 0, 0 },
		.controls = &ctrl,
	};
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	ctrl.value = bitrate;
	rc = xioctl(brd->fd, VIDIOC_S_EXT_CTRLS, &ctrls);
	
	PutBoard(brd);
	return rc;
}

DLLCP int SN_GetBitrate( int boardnum, int *bitrate)
{
	int rc;
	struct v4l2_ext_control ctrl = {
		.id = V4L2_CID_MPEG_VIDEO_BITRATE,
	};
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 1,
		.reserved = { 0, 0 },
		.controls = &ctrl,
	};
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	rc = xioctl(brd->fd, VIDIOC_S_EXT_CTRLS, &ctrls);
	if (rc == 0 && bitrate)
		*bitrate = ctrl.value;

	PutBoard(brd);
	return rc;
}


/* advanced use only. see PDF manual */
DLLCP int SN_SetGopSize( int boardnum, int size)
{
	int rc;
	struct v4l2_ext_control ctrl = {
		.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
	};
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 1,
		.controls = &ctrl,
	};
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	ctrl.value = size;
	rc = ioctl(brd->fd, VIDIOC_S_EXT_CTRLS, &ctrls);

	PutBoard(brd);
	return rc;
}

DLLCP int SN_GetGopSize( int boardnum, int *size)
{
	int rc;
	struct v4l2_ext_control ctrl = {
		.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
	};
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 1,
		.controls = &ctrl,
	};
	struct board *brd = GetBoard(boardnum);

	if (!brd)
		return -ENODEV;
	rc = ioctl(brd->fd, VIDIOC_G_EXT_CTRLS, &ctrls);
	if (rc == 0 && size)
		*size = ctrl.value;

	PutBoard(brd);
	return 0;
}

#define IONLY 1
#define IPONLY 2
#define IPB 3

/* advanced use only. see PDF manual */
DLLCP int SN_SetSeqMode( int boardnum, int seqmode)
{
	int rc;
	struct v4l2_ext_control ctrl[] = {
		{ .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE, },
		{ .id = V4L2_CID_MPEG_VIDEO_B_FRAMES, },
		{ .id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, },
	};
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 3,
		.controls = ctrl,
	};
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;

	rc = ioctl(brd->fd, VIDIOC_G_EXT_CTRLS, &ctrls);
	switch (seqmode) {
	case IONLY:
		ctrl[0].value = 1; /* GOP_SIZE */
		/* fall thru */
	case IPONLY:
		ctrl[1].value = 0; /* B_FRAMES */
		break;
	case IPB:
		ctrl[1].value = 2; /* B_FRAMES */
		break;
	default:
		rc = -EINVAL;
		break;
	}
	ctrl[2].value = 1; /* GOP_CLOSURE */
	if (rc == 0)
		rc = ioctl(brd->fd, VIDIOC_S_EXT_CTRLS, &ctrls);
	
	PutBoard(brd);
	return rc;
}

DLLCP int SN_GetSeqMode( int boardnum, int *seqmode)
{
	int rc;
	struct v4l2_ext_control ctrl[] = {
		{ .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE, },
		{ .id = V4L2_CID_MPEG_VIDEO_B_FRAMES, },
	};
	struct v4l2_ext_controls ctrls = {
		.ctrl_class = V4L2_CTRL_CLASS_MPEG,
		.count = 2,
		.controls = ctrl,
	};
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	rc = ioctl(brd->fd, VIDIOC_G_EXT_CTRLS, &ctrls);
	if (rc == 0 && seqmode) {
		if (ctrl[0].value == 1 && ctrl[1].value == 0)
			*seqmode = 1;
		else if (ctrl[1].value == 0)
			*seqmode = 2;
		else if (ctrl[1].value == 2)
			*seqmode = 3;
	}
	PutBoard(brd);
	return rc;
}

/* returns current encoding type */
DLLCP int SN_GetEncodeType( int boardnum, MIDSRAY_ENCODING *encode)
{
	int rc;
	struct v4l2_format fmt = { .type = V4L2_BUF_TYPE_VIDEO_CAPTURE };
	struct board *brd = GetBoard(boardnum);
	
	if (!brd)
		return -ENODEV;
	rc = ioctl(brd->fd, VIDIOC_G_FMT, &fmt);
	if (rc == 0 && encode) {
		if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG)
			*encode = MIDSRAY_ENCODE_MOTIONJPEG;
		else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MP4V)
			*encode = MIDSRAY_ENCODE_MPEG4;
		else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MPEG){
			struct v4l2_ext_control ctrl = {
				.id = V4L2_CID_MPEG_VIDEO_ENCODING,
			};
			struct v4l2_ext_controls ctrls = {
				.ctrl_class = V4L2_CTRL_CLASS_MPEG,
				.count = 1,
				.controls = &ctrl,
			};
			rc = ioctl(brd->fd, VIDIOC_G_EXT_CTRLS, &ctrls);
			if (rc == 0)
				switch (ctrl.value) {
				case V4L2_MPEG_VIDEO_ENCODING_MPEG_1:
					*encode = MIDSRAY_ENCODE_MPEG1;
					break;
				case V4L2_MPEG_VIDEO_ENCODING_MPEG_2:
					*encode = MIDSRAY_ENCODE_MPEG2;
					break;
				case V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC:
					*encode = MIDSRAY_ENCODE_MPEG4;
					break;
				}
		}
	}

	PutBoard(brd);
	return rc;
}

/* set data retrieval to blocking (1s timeout) */
DLLCP int SN_SetBlocking(int boardnum)
{
	int flags, rc;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	if (brd->capturing) {
		dprintk("%s: can't change while capturing\n", __func__);
		PutBoard(brd);
		return -EBUSY;
	}
	dprintk("%s: board %d\n", __func__, boardnum);

	flags = fcntl(brd->fd, F_GETFL, 0);
	if (flags == -1)
		flags = 0;
	rc = fcntl(brd->fd, F_SETFL, flags & ~O_NONBLOCK);
	if (rc == -1)
		rc = -errno;
	else
		brd->blocking = 1;
	if (brd->pcm)
		FreePCM(brd);
	EnumPCM(brd, boardnum);	/* reopen the pcm */

	PutBoard(brd);
	return 0;
}

/* set data retrieval to polling(immediate return even if no data */
DLLCP int SN_SetPolling(int boardnum)
{
	int flags, rc;
	struct board *brd = GetBoard(boardnum);
	if (!brd)
		return -ENODEV;
	if (brd->capturing) {
		dprintk("%s: can't change while capturing\n", __func__);
		PutBoard(brd);
		return -EBUSY;
	}
		
	dprintk("%s: board %d\n", __func__, boardnum);

	flags = fcntl(brd->fd, F_GETFL, 0);
	if (flags == -1)
		flags = 0;
	rc = fcntl(brd->fd, F_SETFL, flags | O_NONBLOCK);
	if (rc == -1)
		rc = -errno;
	else
		brd->blocking = 0;
	if (brd->pcm)
		FreePCM(brd);
	EnumPCM(brd, boardnum);	/* reopen the pcm */

	PutBoard(brd);
	return rc;
}

/* sets global printf flag in the SDK. Loud output. for debug only */
DLLCP int SN_Debug(int bOnOff)
{
	G_debug = bOnOff ? 1 : 0;
	dprintk("%s: debugging is now on\n", __func__);
}
